package com.dysoft.flesh.gl;

import com.dysoft.bones.DataManager;
import com.dysoft.flesh.RenderContext;

import java.util.concurrent.Callable;

/**
 * @author Sean Micklethwaite
 *         Aug 16, 2010 10:36:40 PM
 */
public class GLDataObject<T> extends DataManager.DataObject<T, GLDataObject.ConstructorData> {
	protected static class ConstructorData {
		public final Object id;
		public final Renderer.Root renderer;
		RenderContext context = null;
		Object strongRef = null;

		public ConstructorData(Object id, Renderer.Root renderer) {
			this.id = id;
			this.renderer = renderer;
		}

		public RenderContext getContext() {
			return context;
		}

		public void setContext(RenderContext context) {
			this.context = context;
		}

		public Object getStrongRef() {
			return strongRef;
		}

		public void setStrongRef(Object strongRef) {
			this.strongRef = strongRef;
		}

		@Override
		public String toString() {
			return id.toString();
		}

		@Override
		public int hashCode() {
			return id.hashCode();
		}

		@Override
		public boolean equals(Object o) {
			return o.equals(this.id);
		}
	}

	final Callable<T> constructor;

	public GLDataObject(Object id, Callable<T> constructor, GLRenderer renderer) {
		super(new ConstructorData(id, renderer));
		this.constructor = constructor;
	}

	@Override
	protected T construct(ConstructorData data) throws Exception {
		if(data.getContext() != null && !data.getContext().isRendering()) {
			return constructor.call();
		} else {
			Renderer.Root renderer = data.getContext() == null ? data.renderer :
					data.getContext().getRenderer();
			if(renderer == null) {
				throw new RuntimeException("Not loadable (no valid renderer): " + data.id);
			}

			renderer.queuePreRenderOp(new Renderer.Operation() {
				public boolean call(RenderContext ctx) throws Exception {
					set(constructor.call());
					return false;
				}
			});
			return null;
		}
	}

	public static class Renderer extends GLDataObject<com.dysoft.flesh.Renderer> implements com.dysoft.flesh.Renderer {
		public Renderer(Object id, Callable<com.dysoft.flesh.Renderer> constructor, GLRenderer renderer) {
			super(id, constructor, renderer);
		}

		public void render(RenderContext ctx) throws NotPresentException {
			get().render(ctx);
		}
	}

	/**
	 * XXX: sharing is not actually tested at all. This is just a name.
	 */
	public static class SharedRenderer extends Renderer {
		public SharedRenderer(Object id, Callable<com.dysoft.flesh.Renderer> constructor) {
			super(id, constructor, null);
		}

		public void render(RenderContext ctx) throws NotPresentException {
			id.setContext(ctx);
			super.render(ctx);
			id.setContext(null);
		}
	}

	public static class RenderComponent<T extends com.dysoft.flesh.RenderComponent> extends GLDataObject<T>
			implements com.dysoft.flesh.RenderComponent {
		public RenderComponent(Object id, Callable<T> constructor, GLRenderer renderer) {
			super(id, constructor, renderer);
		}

		public void activate(RenderContext ctx) throws NotPresentException {
			assert id.getStrongRef() == null;
			com.dysoft.flesh.RenderComponent rc = get();
			rc.activate(ctx);

			// Set strong reference, to keep RC resident while active
			id.setStrongRef(rc);
		}

		public void deactivate(RenderContext ctx) throws NotPresentException {
			com.dysoft.flesh.RenderComponent rc = (com.dysoft.flesh.RenderComponent)id.getStrongRef();
			id.setStrongRef(null);

			assert rc != null;
			rc.deactivate(ctx);
		}
	}

	public static class SharedRenderComponent<T extends com.dysoft.flesh.RenderComponent> extends RenderComponent<T> {
		public SharedRenderComponent(Object id, Callable<T> constructor) {
			super(id, constructor, null);
		}

		public void activate(RenderContext ctx) throws NotPresentException {
			id.setContext(ctx);
			super.activate(ctx);
		}

		public void deactivate(RenderContext ctx) throws NotPresentException {
			super.deactivate(ctx);
			id.setContext(null);
		}
	}
}
